﻿// -----------------------------------------------------------------------
// <copyright file="ISetGenerator.cs" company="none">
// Copyright (c) 2012 M. Alberti, xanatos(at)live.it
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// </copyright>
// -----------------------------------------------------------------------

namespace FluentSerializer.Generators
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    using FluentStatement;
    using FluentStatement.Utilities;

    /// <summary>
    /// 
    /// </summary>
    public static class ISetGenerator
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="serializer"></param>
        /// <param name="parametersType"></param>
        /// <param name="type"></param>
        /// <param name="helper"></param>
        /// <returns></returns>
        public static Descriptor BuildSerializer(Serializer serializer, Type parametersType, Type type, Type helper)
        {
            Descriptor descriptor1 = ICollectionGenerator.BuildSerializer(serializer, parametersType, type, helper);

            Type decoratedTypeWithHelper = helper.IsGenericTypeDefinition ? helper.MakeGenericType(type) : helper;
            Type decoratedType = type;
            Type realType = Serializer.UndecorateType(type);

            Type[] temp = decoratedType.GetInterfaces(typeof(ICollection<>));

            if (temp.Length != 1)
            {
                throw new NotSupportedException();
            }

            Type decoratedInterfaceType = temp[0];

            temp = realType.GetInterfaces(typeof(ICollection<>));

            if (temp.Length != 1)
            {
                throw new NotSupportedException();
            }

            Type realInterfaceType = temp[0];

            InterfaceMapping mapping = realType.IsInterface ? default(InterfaceMapping) : realType.GetInterfaceMap(realInterfaceType);

            Type decoratedSubItemType = decoratedInterfaceType.GetGenericArguments()[0];
            Type realSubItemType = realInterfaceType.GetGenericArguments()[0];

            ParameterExpression item = Expression.Parameter(realType);
            ParameterExpression parameters = parametersType != null ? Expression.Parameter(parametersType) : null;
            ParameterExpression subItem = Expression.Variable(realSubItemType);

            Descriptor subItemDescriptor = Serializer.GetSerializer(serializer, realSubItemType, decoratedSubItemType, helper);

            PropertyInfo count = (from p in realType.GetVisibleProperties()
                                  where p.Name == "Count"
                                  let get = p.GetGetMethod(false)
                                  where get != null && !get.IsStatic && p.GetIndexParameters().Length == 0
                                  select p).FirstOrDefault();

            MemberExpression count3;

            if (count != null)
            {
                count3 = Expression.Property(item, count);
            }
            else
            {
                MethodInfo count2 = mapping.TargetMethods[Array.FindIndex(mapping.InterfaceMethods, p => p.Name == "get_Count")];
                count3 = Expression.Property(item, count2);
            }

            ConstructorInfo constructor = decoratedTypeWithHelper.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
            var helper2 = (ICollectionHelper)constructor.Invoke(null);

            LambdaExpression deserializeExpression;

            /***/
            {
                MethodInfo[] methods = realType.GetVisibleMethods();

                MethodInfo clear = (from p in methods
                                    where p.Name == "Clear" && !p.IsStatic && !p.IsGenericMethod && p.ReturnType == typeof(void) && p.GetParameters().Length == 0
                                    select p).FirstOrDefault();

                if (clear == null)
                {
                    clear = mapping.TargetMethods[Array.FindIndex(mapping.InterfaceMethods, p => p.Name == "Clear")];
                }

                MethodInfo add = (from p in methods
                                  where p.Name == "Add" && !p.IsStatic && !p.IsGenericMethod && p.ReturnType == typeof(bool)
                                  let pars = p.GetParameters()
                                  where pars.Length == 1 && pars[0].ParameterType == realSubItemType
                                  select p).FirstOrDefault();

                if (add == null)
                {
                    add = mapping.TargetMethods[Array.FindIndex(mapping.InterfaceMethods, p => p.Name == "Add")];
                }

                var block = new List<Expression>();

                ParameterExpression size = Expression.Variable(typeof(int));

                if (subItemDescriptor.DeserializationHandling.HasFlag(DeserializationHandling.RequireDispose))
                {
                    block.Add(ExpressionEx.ForEach(item, subItemDescriptor.DeserializeExpression, Expression.Label()));
                }

                block.Add(Expression.Call(item, clear));

                block.Add(Expression.Assign(size, helper2.DeserializeLength(Serializer.ReaderExpression, count3) ?? count3));

                Expression subItemDeserialize = Expression.Call(item, add, Serializer.MakeFromLambda(subItemDescriptor.DeserializeExpression, subItemDescriptor.IsUniversal, Serializer.ReaderExpression, subItem, parameters));

                if (!(subItemDescriptor.DefaultExpression is DefaultExpression))
                {
                    subItemDeserialize = Expression.Block(
                        Expression.Assign(subItem, subItemDescriptor.DefaultExpression),
                        subItemDeserialize);
                }

                ForExpression @for = ExpressionEx.For(Expression.PostDecrementAssign(size), Expression.GreaterThan(size, Serializer.Zero), subItemDeserialize, Expression.Label());

                block.Add(@for);
                block.Add(item);

                BlockExpression expression = Expression.Block(new[] { size, subItem }, block);
                deserializeExpression = Serializer.MakeLambda(expression, descriptor1.IsUniversal, Serializer.ReaderExpression, item, parameters);
            }

            var descriptor2 = new Descriptor(decoratedTypeWithHelper, descriptor1.IsUniversal, deserializeExpression, descriptor1.DeserializationHandling, descriptor1.SerializeExpression, descriptor1.SizeExpression, descriptor1.DisposeExpression);
            return descriptor2;
        }
    }
}